﻿using MsTestUtilities.Errors;
using System;
using System.Collections.Generic;
using System.Linq;

namespace MsTestUtilities.SolutionAnalysis.Testing
{
    public abstract class CollectionAssertionBase<T>
    {
        private bool _negateAssertion;
        protected IEnumerable<T> Elements { get; private set; }

        protected abstract string ElementName(T element);

        internal CollectionAssertionBase()
        {         
        }

        public CollectionAssertionBase(IEnumerable<T> elements)
        {
            Elements = elements;
        }

        public TAssert With<TAssert>()
            where TAssert : CollectionAssertionBase<T>, new()
        {
            var asserter = new TAssert()
            {
                Elements = Elements
            };

            return asserter;
        }

        public CollectionAssertionBase<T> Not()
        {
            _negateAssertion = true;
            return this;
        }

        public void Satisfies<TMember>(Func<T, TMember> memberSelector, Predicate<TMember> predicate, Func<T, string> messageGenerator)
        {
            var errorCollector = new ErrorCollector();

            foreach (var element in Elements)
            {
                var member = memberSelector(element);
                if ((!predicate(member) && !_negateAssertion) || (predicate(member) && _negateAssertion))
                {
                    var error = string.Format("{0}: {1}", ElementName(element), messageGenerator(element));                    
                    errorCollector.AddError(error);
                }
            }

            errorCollector.Evaluate();
        }

        protected void Filter(Predicate<T> predicate)
        {
            Elements = Elements.Where(e => predicate(e));            
        }

        public void Satisfies<TValue>(Func<T, TValue> memberSelector, Predicate<TValue> predicate)
        {            
            Satisfies(
                memberSelector,
                predicate,
                member => string.Format("The predicate was {0}satisfied.", _negateAssertion ? string.Empty : "not "));
        }

        public void AreEqual<TValue>(Func<T, TValue> memberSelector, TValue expected)
        {
            Satisfies(
                memberSelector,
                m => m.Equals(expected),
                member => string.Format("Expected <{0}>. Actual <{1}>", expected, memberSelector(member)));
        }

        public void IsTrue(Func<T, bool> memberSelector, string message = "IsTrue failed.")
        {
            Satisfies(
                memberSelector,
                m => m,
                member => message);
        }

        public void IsFalse(Func<T, bool> memberSelector)
        {
            Satisfies(
                memberSelector,
                m => !m,
                member => "IsFalse failed.");
        }

        public void StartsWith(Func<T, string> memberSelector, string expectedBeginning)
        {
            Satisfies(
                memberSelector,
                m => m.StartsWith(expectedBeginning),
                member => string.Format("Expected string to begin with <{0}>. Actual <{1}>", expectedBeginning, memberSelector(member)));
        }

        public void EndsWith(Func<T, string> memberSelector, string expectedEnd)
        {
            Satisfies(
                memberSelector,
                m => m.EndsWith(expectedEnd),
                member => string.Format("Expected string to end with <{0}>. Actual <{1}>", expectedEnd, memberSelector(member)));
        }

        public void Contains(Func<T, string> memberSelector, string substring)
        {
            Satisfies(
                memberSelector,
                m => m.Contains(substring),
                member => string.Format("Expected string to contain <{0}>. Actual <{1}>", substring, memberSelector(member)));
        }
    }
}
